﻿// Copyright 2009 William P. Hickey Jr. and William U. Walker
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//     
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.ComponentModel;

namespace CSReporter
{
    public class AprsMessage : INotifyPropertyChanged
    {
        private DateTime timeHeard;
        private string fromCall;
        private string toCall;
        private string message;
        private bool acknowledged;
        private int folder;
        private string aprsMsgNumber;
        private string digiPath;
        private bool isCsr;
        private bool isActive = true;

        public event PropertyChangedEventHandler PropertyChanged;

        public DateTime TimeHeard
        {
            get { return timeHeard; }
            set { 
                timeHeard = value;
                this.NotifyPropertyChanged("TimeHeard");
            }
        }

        public string FromCall
        {
            get { return fromCall; }
            set {
                fromCall = value == null ? null : value.Substring(0, Math.Min(value.Length, 10)).Trim().ToUpper();
                this.NotifyPropertyChanged("FromCall");
            }
        }

        public string ToCall
        {
            get { return toCall; }
            set {
                toCall = value == null ? null : value.Substring(0, Math.Min(value.Length, 10)).Trim().ToUpper();
                this.NotifyPropertyChanged("ToCall");
            }
        }

        public string Message
        {
            get { return message; }
            set {
                message = value == null ? null : value.Replace("\r", " ").Substring(0, Math.Min(value.Length, 67)).Trim();
                this.NotifyPropertyChanged("Message");
            }
        }

        public bool Acknowledged
        {
            get { return acknowledged; }
            set { 
                acknowledged = value;
                this.NotifyPropertyChanged("Acknowledged");
            }
        }

        public int Folder
        {
            get { return folder; }
            set { 
                folder = value;
                this.NotifyPropertyChanged("Folder");
            }
        }

        public string AprsMsgNumber
        {
            get { return aprsMsgNumber; }
            set {
                aprsMsgNumber = value == null ? null : value.Substring(0, Math.Min(value.Length, 5)).Trim().ToUpper();
                this.NotifyPropertyChanged("AprsMsgNumber");
            }
        }

        public string DigiPath
        {
            get { return digiPath; }
            set {
                digiPath = value == null ? null : value.Substring(0, Math.Min(value.Length, 100)).Trim().ToUpper();
                this.NotifyPropertyChanged("DigiPath");
            }
        }

        public bool IsCSR
        {
            get { return isCsr; }
            set { 
                isCsr = value;
                this.NotifyPropertyChanged("IsCSR");
            }
        }

        public bool IsActive
        {
            get { return isActive; }
            set
            {
                isActive = value;
                this.NotifyPropertyChanged("IsActive");
            }
        }

        public AprsMessage()
        {
            this.FromCall = "";
            this.ToCall = "";
            this.Message = "";
            this.Acknowledged = false;
            this.Folder = 0;
            this.AprsMsgNumber = "";
            this.DigiPath = "";
            this.IsCSR = false;
        }

        public AprsMessage(DateTime timeHeard, string fromCall, string toCall, string message, int folder, string aprsMsgNumber, string digiPath, bool isCsr)
        {
            this.TimeHeard = timeHeard;
            this.FromCall = fromCall;
            this.ToCall = toCall;
            this.Message = message;
            this.Acknowledged = false;
            this.Folder = folder;
            this.AprsMsgNumber = aprsMsgNumber;
            this.DigiPath = digiPath;
            this.IsCSR = isCsr;
        }

        private void NotifyPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    /// <summary>
    /// Storage and manipulation class for a List of AprsMessage objects.
    /// This class uses a singleton design pattern.
    /// </summary>
    public class MessageList : BindingList<AprsMessage>
    {
        // Static initialization ensures thread safety.
        private static readonly MessageList singletonInstance = new MessageList();

        private MessageList() { }

        public static MessageList GetMessageList()
        {
            return singletonInstance;
        }

        /// <summary>
        /// Adds a AprsMessage object to the end of the List<AprsMessage> collection.
        /// </summary>
        public void AddMessage(AprsMessage aprsMessage)
        {
            bool found = false;

            foreach(AprsMessage message in Items)
            {
                if (message.Folder != 1 & message.ToCall == aprsMessage.ToCall & message.FromCall == aprsMessage.FromCall & message.AprsMsgNumber == aprsMessage.AprsMsgNumber)
                    found = true;
            }
            if (!found)
            {
                Add(aprsMessage);
            }
        }

        public void AddCSReport(string fromCall, string toCall, string digiPath, string station, string reportId, DateTime incidentTime, string latitude, string longitude,
            string statusCode, string cityCode, string stateCode, string streetNumber, string streetDirection, string streetName,
            string crossRoad1, string crossRoad2, string detailText)
        {
            string aprsIncidentTime = incidentTime.ToUniversalTime().Day.ToString().PadLeft(2, '0') +
                                      incidentTime.ToUniversalTime().Hour.ToString().PadLeft(2, '0') +
                                      incidentTime.ToUniversalTime().Minute.ToString().PadLeft(2, '0') + "z";
            DateTime curTime = DateTime.Now;
            string aprsMsgNumber;

            if (toCall.Trim().Length > 0 & station.Trim().Length > 0 & reportId.Trim().Length > 0 & latitude.Length == 8 & longitude.Length == 9 & statusCode.Trim().Length > 0 & statusCode.Trim().Length <= 22 & incidentTime >= DateTime.Now.AddHours(-24) & incidentTime <= DateTime.Now)
            {
                aprsMsgNumber = String.Format("{0:X}", (curTime.Hour * 10000) + (curTime.Minute * 1000) + (curTime.Second));
                string statusReport1 = "^1" + station.Trim().ToUpper().PadRight(10, ' ') + reportId.Trim().ToUpper().PadLeft(4, '0') + aprsIncidentTime + latitude + longitude + statusCode.Trim().ToUpper().PadRight(22, ' ') + cityCode.Trim().ToUpper().PadRight(3, ' ') + stateCode.Trim().ToUpper().PadRight(2, ' ');
                AddMessage(new AprsMessage(DateTime.Now, fromCall.Trim().ToUpper(), toCall.Trim().ToUpper(), statusReport1, 6, aprsMsgNumber, digiPath.Trim().ToUpper(), true));

                if (streetNumber.Trim().Length > 0 | streetDirection.Trim().Length > 0 | streetName.Trim().Length > 0)
                {
                    aprsMsgNumber = String.Format("{0:X}", (curTime.Hour * 10000) + (curTime.Minute * 1000) + (curTime.Second + 1));
                    string statusReport2 = "^2" + station.Trim().ToUpper().PadRight(10, ' ') + reportId.Trim().ToUpper().PadLeft(4, '0') + streetNumber.Trim() + "," + streetDirection.Trim() + "," + streetName.Trim();
                    AddMessage(new AprsMessage(DateTime.Now, fromCall.Trim().ToUpper(), toCall.Trim().ToUpper(), statusReport2, 6, aprsMsgNumber, digiPath.Trim().ToUpper(), true));
                }

                if (crossRoad1.Trim().Length > 0 | crossRoad2.Trim().Length > 0)
                {
                    aprsMsgNumber = String.Format("{0:X}", (curTime.Hour * 10000) + (curTime.Minute * 1000) + (curTime.Second + 2));
                    string statusReport3 = "^3" + station.Trim().ToUpper().PadRight(10, ' ') + reportId.Trim().ToUpper().PadLeft(4, '0') + crossRoad1.Trim() + "," + crossRoad2.Trim();
                    AddMessage(new AprsMessage(DateTime.Now, fromCall.Trim().ToUpper(), toCall.Trim().ToUpper(), statusReport3, 6, aprsMsgNumber, digiPath.Trim().ToUpper(), true));
                }

                if (detailText.Trim().Length > 0)
                {
                    // Replace new line characters with a space.
                    detailText = detailText.Replace("\n", " ");
                    // Remove all other non-printable characters.
                    detailText = Regex.Replace(detailText, "[\u0000-\u001F\u0080-\u00FF]", "");
                    // Determine how many 67 byte messages must be sent.
                    int segments = (int)Math.Ceiling((double)detailText.Trim().Length / 51);

                    if (segments > 0)
                    {
                        string[] message = new string[segments];
                        string[] aprsMsgNumbers = new string[segments];

                        for (int index = 0; index < segments; index++)
                        {
                            // Format the message.
                            message[index] = "^C" + station.Trim().ToUpper().PadRight(10, ' ') + reportId.Trim().ToUpper().PadLeft(4, '0') + detailText.Trim().Substring((index * 51), Math.Min(51, detailText.Trim().Length - (index * 51)));
                            // Create the message id string.
                            aprsMsgNumbers[index] = String.Format("{0:X}", (curTime.Hour * 10000) + (curTime.Minute * 1000) + (curTime.Second + 3 + index));
                            // Record the message.
                            AddMessage(new AprsMessage(DateTime.Now, fromCall.Trim().ToUpper(), toCall.Trim().ToUpper(), message[index], 6, aprsMsgNumbers[index], digiPath.Trim().ToUpper(), true));
                        }
                    }
                }
            }
        }

        public bool Acknowledge(string toCall, string fromCall, string aprsMsgNumber)
        {
            bool found = false;

            foreach (AprsMessage message in Items)
            {
                if (message.ToCall == toCall.Trim().ToUpper() & message.FromCall == fromCall.Trim().ToUpper() & message.AprsMsgNumber == aprsMsgNumber.Trim().ToUpper())
                {
                    message.Acknowledged = true;
                    found = true;
                }
            }
            return found;
        }

        public bool ChangeFolder(string toCall, string fromCall, string aprsMsgNumber, int oldFolder, int newFolder)
        {
            bool found = false;

            foreach (AprsMessage message in Items)
            {
                if (message.Folder == oldFolder & message.ToCall == toCall.Trim().ToUpper() & message.FromCall == fromCall.Trim().ToUpper() & message.AprsMsgNumber == aprsMsgNumber.Trim().ToUpper())
                {
                    message.Folder = newFolder;
                    found = true;
                }
            }
            return found;
        }

        public bool Delete(string fromCall, string aprsMsgNumber)
        {
            bool found = false;

            for (int index = 0; index < Count; index++)
            {
                if (Items[index].FromCall == fromCall.Trim().ToUpper() & Items[index].AprsMsgNumber == aprsMsgNumber.Trim().ToUpper())
                {
                    base.RemoveAt(index);
                    found = true;
                }
            }
            return found;
        }

        public bool IsMessageAcked(string toCall, string fromCall, string aprsMsgNumber)
        {
            foreach (AprsMessage message in Items)
            {
                if (message.ToCall == toCall.Trim().ToUpper() & message.FromCall == fromCall.Trim().ToUpper() & message.AprsMsgNumber == aprsMsgNumber.Trim().ToUpper())
                    return message.Acknowledged;
            }
            return false;
        }

        public void ReadXml(string fileName)
        {
            if (File.Exists(fileName))
            {
                Clear();
                XmlSerializer deserializer = new XmlSerializer(typeof(List<AprsMessage>));
                using (TextReader textReader = new StreamReader(fileName))
                    foreach (AprsMessage ic in (List<AprsMessage>)deserializer.Deserialize(textReader))
                        AddMessage(ic);
            }
        }

        public void WriteXml(string fileName)
        {
            if (Directory.GetParent(fileName).Exists)
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<AprsMessage>));
                using (TextWriter textWriter = new StreamWriter(fileName))
                    serializer.Serialize(textWriter, Items);
            }
        }
    }
}
